﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Pavilion
{
    public class PolygonBox
    {
        public event Action<PolygonBox> PolygonBoxChanged;
        public event Action<Vector2, int> PointAdded;

        public int Count { get { return pointCollection.Count; } }
        public Vector2 this[int index] { get { return pointCollection[index]; } set { Validate(); pointCollection[index] = value; } }
        public Rectangle Rectangle { get { return rectangle; } }

        private bool isChanging;
        private List<Vector2> pointCollection;
        private Rectangle rectangle;

        public PolygonBox()
        {
            this.rectangle = new Rectangle();
            this.pointCollection = new List<Vector2>();
        }

        public PolygonBox(IEnumerable<Vector2> points) : this()
        {
            this.pointCollection.AddRange(points);
        }

        public void BeginChanges()
        {
            isChanging = true;
        }

        public void EndChanges()
        {
            if (!isChanging)
                return;

            isChanging = false;
            DetermineRectangleBounds();
            OnPolygonBoxChanged();
        }

        public void Add(Vector2 point)
        {
            Validate();
            pointCollection.Add(point);
            OnPointAdded(point, pointCollection.Count - 1);
        }

        public void AddRange(IEnumerable<Vector2> points)
        {
            Validate();

            int prevousCount = this.pointCollection.Count;
            int addedPointsCount = points.Count();

            this.pointCollection.AddRange(points);
            
            for (int i = 0; i < addedPointsCount; i++)
                OnPointAdded(this.pointCollection[prevousCount + i], prevousCount + i);
        }

        public bool Remove(Vector2 point)
        {
            Validate();
            return pointCollection.Remove(point);
        }

        public void RemoveAt(int index)
        {
            Validate();
            pointCollection.RemoveAt(index);
        }

        public void Clear()
        {
            Validate();
            pointCollection.Clear();
        }

        public void TransformPoints(float xDistance, float yDistance)
        {
            Validate();
 
            for (int i = 0; i < pointCollection.Count; i++)
                pointCollection[i] = new Vector2(pointCollection[i].X + xDistance, pointCollection[i].Y + yDistance);
        }

        public bool Intersects(PolygonBox polygonBox)
        {
            if (!Intersects(this.Rectangle, polygonBox.Rectangle))
                return false;

            if (polygonBox.pointCollection.Count == 0)
                return false;

            for (int i = 0; i < this.pointCollection.Count; i++)
            {
                if (IsInsidePolygon(polygonBox, this.pointCollection[i].X, pointCollection[i].Y))
                    return true;
            }

            return IsInsidePolygon(this, polygonBox.pointCollection[0].X, polygonBox.pointCollection[0].Y);
        }

        public override string ToString()
        {
            if (pointCollection.Count == 0)
                return "Empty PolygonBox";

            string toString = "";

            foreach (Vector2 point in pointCollection)
                toString += "(" + point + ") ";

            return toString;
        }

        private void Validate()
        {
            if (!isChanging)
                throw new InvalidOperationException("Must call BeginChanges first before making changes.");
        }

        private void DetermineRectangleBounds()
        {
            if (pointCollection.Count == 0)
                return;

            float left = pointCollection[0].X;
            float right = pointCollection[0].X;
            float top = pointCollection[0].Y;
            float bottom = pointCollection[0].Y;

            for (int i = 1; i < pointCollection.Count; i++)
            {
                if (pointCollection[i].X < left)
                    left = pointCollection[i].X;
                else if (pointCollection[i].X > right)
                    right = pointCollection[i].X;

                if (pointCollection[i].Y < top)
                    top = pointCollection[i].Y;
                else if (pointCollection[i].Y > bottom)
                    bottom = pointCollection[i].Y;
            }

            rectangle.X = (int)left;
            rectangle.Y = (int)top;
            rectangle.Width = (int)(right - left);
            rectangle.Height = (int)(bottom - top);
        }

        private void OnPolygonBoxChanged()
        {
            if (PolygonBoxChanged != null)
                PolygonBoxChanged(this);
        }

        private void OnPointAdded(Vector2 point, int index)
        {
            if (PointAdded != null)
                PointAdded(point, index);
        }

        public static bool IsInsidePolygon(PolygonBox polygonBox, float x, float y)
        {
            List<Vector2> points = polygonBox.pointCollection;
            int polySides = points.Count;
            int i, j = polySides - 1;
            bool oddNodes = false;

            //This is a more efficient way, but less accurate since it doesn't cover all polygons. But it covers most of them, especially the more common ones.
            //for (i = 0; i < polySides; i++)
            //{
            //    if ((points[i].Y < y && points[j].Y >= y || points[j].Y < y && points[i].Y >= y) && (points[i].X <= x || points[j].X <= x))
            //    {
            //        if (points[i].X + (y - points[i].Y) / (points[j].Y - points[i].Y) * (points[j].X - points[i].X) < x)
            //            oddNodes = !oddNodes;
            //    }
            //    j = i;
            //}

            for (i = 0; i < polySides; i++)
            {
                if (points[i].Y < y && points[j].Y >= y || points[j].Y < y && points[i].Y >= y)
                {
                    if (points[i].X + (y - points[i].Y) / (points[j].Y - points[i].Y) * (points[j].X - points[i].X) < x)
                        oddNodes = !oddNodes;
                }
                j = i;
            }

            return oddNodes;
        }

        public static bool Intersects(Rectangle a, Rectangle b)
        {
            if (a.Right > b.Left && a.Left < b.Right && a.Top < b.Bottom && a.Bottom > b.Top)
                return true;

            return false;
        }
    }
}
